package app.travel.dao;

import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.dbutils.BeanProcessor;

import app.caretrack.model.BaseModel;

public class SimpleBeanProcessor extends BeanProcessor {
    @Override
    public <T> List<T> toBeanList(ResultSet rs, Class<T> type) throws SQLException {
        List results = new ArrayList();

        if (!(rs.next())) {
            return results;
        }

        PropertyDescriptor[] props = null;
        try {
            props = Introspector.getBeanInfo(type).getPropertyDescriptors();
        } catch (IntrospectionException e) {
            throw new SQLException(e);
        }
        ResultSetMetaData rsmd = rs.getMetaData();
        int[] columnToProperty = mapColumnsToProperties(rsmd, props);
        do {
            results.add(createBean(rs, type, props, columnToProperty));
        } while (rs.next());

        return results;
    }

    @Override
    protected int[] mapColumnsToProperties(ResultSetMetaData rsmd, PropertyDescriptor[] props) throws SQLException {
        int cols = rsmd.getColumnCount();
        int[] columnToProperty = new int[cols + 1];
        Arrays.fill(columnToProperty, -1);

        for (int col = 1; col <= cols; ++col) {
            String columnName = rsmd.getColumnLabel(col);
            if ((null == columnName) || (0 == columnName.length()))
                columnName = rsmd.getColumnName(col);

            for (int i = 0; i < props.length; ++i) {
                if (columnName.equalsIgnoreCase(props[i].getName())
                        || columnName.equalsIgnoreCase(props[i].getName() + "_Id")) {
                    columnToProperty[col] = i;
                    break;
                }
            }
        }

        return columnToProperty;
    }

    private <T> T createBean(ResultSet rs, Class<T> type, PropertyDescriptor[] props, int[] columnToProperty)
            throws SQLException {
        T bean = newInstance(type);

        for (int i = 1; i < columnToProperty.length; ++i) {
            if (columnToProperty[i] == -1) {
                continue;
            }

            PropertyDescriptor prop = props[columnToProperty[i]];
            Class propType = prop.getPropertyType();

            System.out.println(prop.getName());
            Object value = processColumn(rs, i, propType);

            if ((propType != null) && (value == null) && (propType.isPrimitive())) {
                // value = primitiveDefaults.get(propType);
            }

            this.callSetter(bean, prop, value);
        }

        return bean;
    }

    @Override
    protected Object processColumn(ResultSet rs, int index, Class<?> propType) throws SQLException {
        if ((!(propType.isPrimitive())) && (rs.getObject(index) == null)) {
            return null;
        }

        if (propType.equals(String.class))
            return rs.getString(index);

        if ((propType.equals(Integer.TYPE)) || (propType.equals(Integer.class))) {
            return Integer.valueOf(rs.getInt(index));
        }
        if ((propType.equals(Boolean.TYPE)) || (propType.equals(Boolean.class))) {
            return Boolean.valueOf(rs.getBoolean(index));
        }
        if ((propType.equals(Long.TYPE)) || (propType.equals(Long.class)))
            return Long.valueOf(rs.getLong(index));

        if ((propType.equals(Double.TYPE)) || (propType.equals(Double.class))) {
            return Double.valueOf(rs.getDouble(index));
        }
        if ((propType.equals(Float.TYPE)) || (propType.equals(Float.class))) {
            return Float.valueOf(rs.getFloat(index));
        }
        if ((propType.equals(Short.TYPE)) || (propType.equals(Short.class))) {
            return Short.valueOf(rs.getShort(index));
        }
        if ((propType.equals(Byte.TYPE)) || (propType.equals(Byte.class)))
            return Byte.valueOf(rs.getByte(index));

        if (propType.equals(Timestamp.class)) {
            return rs.getTimestamp(index);
        } else if (propType.getName().startsWith("app.caretrack.model")) {
            BaseModel model = (BaseModel) newInstance(propType);
            System.out.println(propType);
            model.setId(rs.getInt(index));
            return model;
        }
        return rs.getObject(index);
    }

    private void callSetter(Object target, PropertyDescriptor prop, Object value) throws SQLException {
        Method setter = prop.getWriteMethod();

        if (setter == null) {
            return;
        }

        Class[] params = setter.getParameterTypes();
        try {
            if ((value != null) && (value instanceof Date)) {
                if (params[0].getName().equals("java.sql.Date")) {
                    value = new Date(((Date) value).getTime());
                } else if (params[0].getName().equals("java.sql.Time")) {
                    value = new Time(((Date) value).getTime());
                } else if (params[0].getName().equals("java.sql.Timestamp")) {
                    value = new Timestamp(((Date) value).getTime());
                }
            }

            if (isCompatibleType(value, params[0])) {
                setter.invoke(target, new Object[]{ value });
            } else {
                throw new SQLException("Cannot set " + prop.getName() + ": incompatible types.");
            }

        } catch (Exception e) {
            throw new SQLException("Cannot set " + prop.getName() + ": " + e.getMessage(), e);
        }
    }

    private boolean isCompatibleType(Object value, Class<?> type) {
        if ((value == null) || (type.isInstance(value)))
            return true;

        if ((type.equals(Integer.TYPE)) && (Integer.class.isInstance(value)))
            return true;

        if ((type.equals(Long.TYPE)) && (Long.class.isInstance(value)))
            return true;

        if ((type.equals(Double.TYPE)) && (Double.class.isInstance(value)))
            return true;

        if ((type.equals(Float.TYPE)) && (Float.class.isInstance(value)))
            return true;

        if ((type.equals(Short.TYPE)) && (Short.class.isInstance(value)))
            return true;

        if ((type.equals(Byte.TYPE)) && (Byte.class.isInstance(value)))
            return true;

        if ((type.equals(Character.TYPE)) && (Character.class.isInstance(value))) {
            return true;
        }

        return ((type.equals(Boolean.TYPE)) && (Boolean.class.isInstance(value)));
    }
}
